package com.atguigu.gmall.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.cart.client.CartFeignClient;
import com.atguigu.gmall.cart.model.CartInfo;
import com.atguigu.gmall.common.constant.MqConst;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.service.RabbitService;
import com.atguigu.gmall.common.util.HttpClientUtil;
import com.atguigu.gmall.enums.model.OrderStatus;
import com.atguigu.gmall.enums.model.PaymentType;
import com.atguigu.gmall.enums.model.ProcessStatus;
import com.atguigu.gmall.order.model.OrderDetail;
import com.atguigu.gmall.order.model.OrderInfo;
import com.atguigu.gmall.order.mapper.OrderInfoMapper;
import com.atguigu.gmall.order.service.OrderDetailService;
import com.atguigu.gmall.order.service.OrderInfoService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.user.client.UserFeignClient;
import com.atguigu.gmall.user.model.UserAddress;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang.StringUtils;
import org.checkerframework.checker.units.qual.C;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 订单表 订单表 业务实现类
 *
 * @author atguigu
 * @since 2023-03-10
 */
@Service
public class OrderInfoServiceImpl extends ServiceImpl<OrderInfoMapper, OrderInfo> implements OrderInfoService {

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private CartFeignClient cartFeignClient;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private OrderDetailService orderDetailService;

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ThreadPoolExecutor executor;

    @Autowired
    private RabbitService rabbitService;


    /**
     * 仓库管理系统调用接口基础地址
     */
    @Value("${ware.url}")
    private String wareUrl;

    /**
     * 汇总渲染页面所有的数据
     *
     * @param userId 用户ID
     * @return
     */
    @Override
    public Map<String, Object> tradeDatas(String userId) {
        System.err.println("userId:" + userId);
        Map<String, Object> tradeMap = new HashMap<>();
        //1.远程调用用户微服务得到当前用户收件地址列表 userAddressList
        List<UserAddress> userAddressList = userFeignClient.getUserAddressListByUserId(Long.valueOf(userId));
        if (!CollectionUtils.isEmpty(userAddressList)) {
            tradeMap.put("userAddressList", userAddressList);
        }
        //2.远程调用购物车微服务得到当前用户购物车中选中商品列表-商品清单 detailArrayList
        List<CartInfo> cartCheckedList = cartFeignClient.getCartCheckedList(Long.valueOf(userId));
        if (!CollectionUtils.isEmpty(cartCheckedList)) {
            //2.0 将得到集合中购物车对象CartInfo转为订单明细对象OrderDetail
            List<OrderDetail> orderDetailList = cartCheckedList.stream().map(cartInfo -> {
                OrderDetail orderDetail = new OrderDetail();
                orderDetail.setSkuId(cartInfo.getSkuId());
                orderDetail.setSkuName(cartInfo.getSkuName());
                orderDetail.setSkuNum(cartInfo.getSkuNum());
                orderDetail.setImgUrl(cartInfo.getImgUrl());
                orderDetail.setOrderPrice(productFeignClient.getSkuPrice(cartInfo.getSkuId()));
                return orderDetail;
            }).collect(Collectors.toList());
            tradeMap.put("detailArrayList", orderDetailList);

            //2.1 封装总商品数量 totalNum
            tradeMap.put("totalNum", cartCheckedList.size());
            //2.2 封装订单总金额 totalAmount
            OrderInfo orderInfo = new OrderInfo();
            orderInfo.setOrderDetailList(orderDetailList);
            //调用计算订单总金额方法
            orderInfo.sumTotalAmount();
            tradeMap.put("totalAmount", orderInfo.getTotalAmount());
        }
        //3. 避免订单重复提交,渲染订单确认页面中回显-业务唯一流水号 tradeNo
        String tradeNo = this.generateTradeNo(userId);
        tradeMap.put("tradeNo", tradeNo);
        return tradeMap;
    }


    /**
     * 提交订单
     *
     * @param orderInfo
     * @param tradeNo   用户提交流水号
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long submitOrder(OrderInfo orderInfo, String tradeNo) {
        //1.避免用户采用浏览器回退避免订单多次提交
        String userId = orderInfo.getUserId().toString();
        //Boolean flag = this.checkTradeNo(userId, tradeNo);
        //if (!flag) {
        //    throw new RuntimeException("页面提交参数有误!");
        //}
        //删除流水号
        //this.deleteTradeNo(userId);
        //判断跟删除流水号不是原子操作
        //采用LUA脚本保证 判断跟删除原子操作
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText("if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                "then\n" +
                "    return redis.call(\"del\",KEYS[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end");
        String redisKey = RedisConst.USER_KEY_PREFIX + userId + ":tradeNo";
        redisScript.setResultType(Long.class);
        Long flag = (Long) redisTemplate.execute(redisScript, Arrays.asList(redisKey), tradeNo);
        if (flag.intValue() == 0) {
            throw new RuntimeException("页面提交参数有误!");
        }

        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        List<String> errorMessage = new ArrayList<>();
        //2. 调用第三方库存系统(仓储服务)接口进行验证商品库存
        List<CompletableFuture> completableFuturesList = new ArrayList<>();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            orderDetailList.stream().forEach(orderDetail -> {
                //调用仓库管理系统验证库存
                CompletableFuture<Void> stockCompletableFuture = CompletableFuture.runAsync(() -> {
                    boolean hashStock = this.checkStock(orderDetail.getSkuId(), orderDetail.getSkuNum());
                    if (!hashStock) {
                        errorMessage.add("商品:" + orderDetail.getSkuName() + "库存不足!");
                    }
                }, executor);


                CompletableFuture<Void> priceCompletableFuture = CompletableFuture.runAsync(() -> {
                    //3. 调用商品微服务获取商品价格,验证商品价格是否发生变化
                    BigDecimal skuPrice = productFeignClient.getSkuPrice(orderDetail.getSkuId());
                    if (orderDetail.getOrderPrice().compareTo(skuPrice) != 0) {
                        //3.1 将购物车中商品价格改为最新
                        String cartKey = RedisConst.USER_KEY_PREFIX + userId + RedisConst.USER_CART_KEY_SUFFIX;
                        BoundHashOperations<String, String, CartInfo> hashOps = redisTemplate.boundHashOps(cartKey);
                        CartInfo cartInfo = hashOps.get(orderDetail.getSkuId().toString());
                        cartInfo.setSkuPrice(skuPrice);
                        hashOps.put(cartInfo.getSkuId().toString(), cartInfo);
                        errorMessage.add("商品:" + orderDetail.getSkuName() + "价格已失效!");
                    }
                }, executor);

                completableFuturesList.add(stockCompletableFuture);
                completableFuturesList.add(priceCompletableFuture);
            });
        }

        //等待所有异步任务全部执行完毕
        CompletableFuture.allOf(completableFuturesList.toArray(new CompletableFuture[completableFuturesList.size()])).join();

        //判断错误信息中是否有数据 有数据:业务验证失败 结束
        if (!CollectionUtils.isEmpty(errorMessage)) {
            throw new RuntimeException(errorMessage.stream().collect(Collectors.joining(",")));
        }

        //4.保存订单信息
        this.saveOrder(orderInfo, "1");

        //TODO 订单中包含商品SkuID从购物车中删除 为了后续方便开发,暂时不删除

        //6. 发送延迟关闭订单消息 到 RabbitMQ  普通商城订单关单失效24小时
        rabbitService.sendDelayMessage(MqConst.EXCHANGE_DIRECT_ORDER_CANCEL, MqConst.ROUTING_ORDER_CANCEL, orderInfo.getId(), 30);
        return orderInfo.getId(); //返回订单ID
    }


    /**
     * 生成流水号
     *
     * @param userId
     * @return
     */
    @Override
    public String generateTradeNo(String userId) {
        String uuid = UUID.randomUUID().toString().replaceAll("-", "");
        String redisKey = RedisConst.USER_KEY_PREFIX + userId + ":tradeNo";
        redisTemplate.opsForValue().set(redisKey, uuid, 1, TimeUnit.DAYS);
        return uuid;
    }

    /**
     * 验证用户提交流水号跟Redis中流水号是否一致
     *
     * @param userId  用户ID
     * @param tradeNo 前端页面提交流水号
     * @return
     */
    @Override
    public Boolean checkTradeNo(String userId, String tradeNo) {
        //1.先查询redis中流水号是否有值
        String redisKey = RedisConst.USER_KEY_PREFIX + userId + ":tradeNo";
        String redisTradeNo = (String) redisTemplate.opsForValue().get(redisKey);
        if (StringUtils.isBlank(redisTradeNo)) {
            return false;
        }
        //2.判断流水号是否一致
        return redisTradeNo.equals(tradeNo);
    }

    /**
     * 删除流水号
     *
     * @param userId
     */
    @Override
    public void deleteTradeNo(String userId) {
        String redisKey = RedisConst.USER_KEY_PREFIX + userId + ":tradeNo";
        redisTemplate.delete(redisKey);
    }


    /**
     * 调用第三方仓库存储系统进行验证商品库存是否充足
     *
     * @param skuId
     * @param skuNum
     * @return
     */
    @Override
    public boolean checkStock(Long skuId, Integer skuNum) {
        //1.按照仓储系统提供http接口发起http请求
        String url = wareUrl + "/hasStock?skuId=" + skuId + "&num=" + skuNum;
        String result = HttpClientUtil.doGet(url);
        if (StringUtils.isNotBlank(result) && result.equals("1")) {
            return true;
        }
        return false;
    }

    /**
     * 查询当前登录用户订单列表(包括订单明细)
     *
     * @param iPage
     * @param userId
     * @param status
     * @return
     */
    @Override
    public IPage<OrderInfo> getOrderList(IPage<OrderInfo> iPage, String userId, String status) {
        //1.获取操作订单持久层对象
        OrderInfoMapper orderInfoMapper = this.getBaseMapper();

        //2.调用自定义SQL查询
        //if (StringUtils.isBlank(status)) {
        //    OrderStatus orderStatus = OrderStatus.UNPAID;
        //    status = orderStatus.name();
        //}
        iPage = orderInfoMapper.getOrderList(iPage, status, userId);
        return iPage;
    }


    /**
     * 关闭订单
     *
     * @param orderId
     */
    @Override
    public void execExpiredOrder(Long orderId) {
        this.updateOrderStatus(orderId, ProcessStatus.CLOSED);
    }


    /**
     * 根据订单ID修改为指定订单状态
     *
     * @param orderId
     * @param processStatus
     */
    @Override
    public void updateOrderStatus(Long orderId, ProcessStatus processStatus) {
        OrderInfo orderInfo = new OrderInfo();
        orderInfo.setId(orderId);
        orderInfo.setProcessStatus(processStatus.name());
        orderInfo.setOrderStatus(processStatus.getOrderStatus().name());
        this.updateById(orderInfo);
    }

    /**
     * 根据订单编号查询订单信息,订单明细
     *
     * @param outTradeNo
     * @return
     */
    @Override
    public OrderInfo getOrderInfoByOutradeNo(String outTradeNo) {
        //1.根据订单编号查询订单信息
        LambdaQueryWrapper<OrderInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(OrderInfo::getOutTradeNo, outTradeNo);
        OrderInfo orderInfo = this.getOne(queryWrapper);
        if (orderInfo != null) {
            //2.根据订单ID查询订单明细
            LambdaQueryWrapper<OrderDetail> detailLambdaQueryWrapper = new LambdaQueryWrapper<>();
            detailLambdaQueryWrapper.eq(OrderDetail::getOrderId, orderInfo.getId());
            List<OrderDetail> orderDetailList = orderDetailService.list(detailLambdaQueryWrapper);
            orderInfo.setOrderDetailList(orderDetailList);
        }
        return orderInfo;
    }

    /**
     * 构建扣减库存消息,通知库存系统库存锁定/扣减
     *
     * @param outTradeNo
     */
    @Override
    public void sendDeductStockMsg(String outTradeNo) {
        //1.根据订单编号/订单ID查询订单,跟订单明细
        OrderInfo orderInfo = this.getOrderInfoByOutradeNo(outTradeNo);

        //2.将得到订单信息封装为库存系统所需Map对象
        if (orderInfo != null) {
            Map<String, Object> stockMap = initWareMap(orderInfo);

            //3.发送扣减库存消息到MQ,通知库存系统进行商品库存锁定
            rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_WARE_STOCK, MqConst.ROUTING_WARE_STOCK, JSON.toJSONString(stockMap));
        }
    }


    /**
     * 将订单信息封装为库存系统所需要Map集合对象
     *
     * @param orderInfo
     * @return
     */
    private Map<String, Object> initWareMap(OrderInfo orderInfo) {
        HashMap<String, Object> mapResult = new HashMap<>();
        //1.封装Map中订单信息
        mapResult.put("orderId", orderInfo.getId());
        mapResult.put("consignee", orderInfo.getConsignee());
        mapResult.put("consigneeTel", orderInfo.getConsigneeTel());
        mapResult.put("orderComment", orderInfo.getOrderComment());
        mapResult.put("orderBody", orderInfo.getTradeBody());
        mapResult.put("deliveryAddress", orderInfo.getDeliveryAddress());
        //orderInfo.getPaymentWay()=====ALIPAY 库存系统1:在线支付
        mapResult.put("paymentWay", "1");
        //TODO新增,指定当前订单出货仓库ID
        mapResult.put("wareId", orderInfo.getWareId());

        //2.封装Map中订单详情信息
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            List<Map<String, Object>> details = orderDetailList.stream().map(orderDetail -> {
                Map<String, Object> detailMap = new HashMap<>();
                detailMap.put("skuId", orderDetail.getSkuId());
                detailMap.put("skuNum", orderDetail.getSkuNum());
                detailMap.put("skuName", orderDetail.getSkuName());
                return detailMap;
            }).collect(Collectors.toList());
            mapResult.put("details", details);
        }
        return mapResult;
    }


    /**
     * 拆单业务处理
     * 1.根据订单ID获取原始订单以及原始订单明细列表
     * 2.按照库存系统提交仓库跟SKu对照关系确定拆分订单规则
     * 3.生成新的子订单,新子订单订单明细,将数据保存
     * 4.拆单后更新原始订单状态:SPLIT
     * 5.按照库存系统要求响应数据  注意:返回订单信息中一定包含: wareId 订单对应发货仓库ID
     *
     * @param orderId    订单ID
     * @param wareSkuMap [{"wareId":"1","skuIds":["28","29"]},{"wareId":"2","skuIds":["24"]}]
     * @return
     */
    @Override
    public String orderSplit(String orderId, String wareSkuMap) {
        //1.根据订单ID获取原始订单以及原始订单明细列表
        OrderInfo orderInfoOrigin = this.getOrderInfo(Long.valueOf(orderId));
        List<OrderDetail> orderDetailOriginList = orderInfoOrigin.getOrderDetailList();
        if (orderInfoOrigin != null && StringUtils.isNotBlank(wareSkuMap) && !CollectionUtils.isEmpty(orderDetailOriginList)) {
            //2.按照库存系统提交仓库跟SKu对照关系确定拆分订单规则
            List<OrderInfo> allSubOrderInfoList = new ArrayList<>();
            //2.1 将库存系统提交仓库跟SKU对应关系数组转为集合
            List<Map> wareSkuIdMap = JSON.parseArray(wareSkuMap, Map.class);  //该list长度就是拆单数量
            //每循环一次生成新子订单,以及新订单明细
            wareSkuIdMap.stream().forEach(skuWareMap -> {
                //2.2 获取当前订单仓库ID 新订单对应仓库ID
                String wareId = (String) skuWareMap.get("wareId");
                //2.3 获取当前仓库包括SkuID集合 -- 新订单中包含订单明细中商品ID
                List<String> skuIds = (List<String>) skuWareMap.get("skuIds");
                //2.4 遍历原始订单明细列表 对新订单中商品进行过滤
                List<OrderDetail> subOrderDetailList = orderDetailOriginList.stream().filter(orderDetailOrigin -> {
                    return skuIds.contains(orderDetailOrigin.getSkuId().toString());
                }).collect(Collectors.toList());

                //3.生成新的子订单,新子订单订单明细,将数据保存
                OrderInfo subOrderInfo = new OrderInfo();
                //3.1 将原始订单信息拷贝到新订单对象中
                BeanUtils.copyProperties(orderInfoOrigin, subOrderInfo);

                //3.2 重新为新订单的属性进行赋值:订单ID 订单金额 上级订单ID 仓库ID
                subOrderInfo.setId(null);
                subOrderInfo.setOrderDetailList(subOrderDetailList);
                subOrderInfo.sumTotalAmount();
                subOrderInfo.setParentOrderId(orderInfoOrigin.getId());
                subOrderInfo.setWareId(wareId);
                //3.3 将新子订单进行保存
                this.save(subOrderInfo);

                //3.4 将新子订单订单明细进行保存-关联新子订单
                for (OrderDetail orderDetail : subOrderDetailList) {
                    orderDetail.setOrderId(subOrderInfo.getId());
                }
                //3.5 将子订单包含订单明细保存
                orderDetailService.saveBatch(subOrderDetailList);
                allSubOrderInfoList.add(subOrderInfo);
            });

            //4.拆单后更新原始订单状态:SPLIT
            this.updateOrderStatus(orderInfoOrigin.getId(), ProcessStatus.SPLIT);

            //5.按照库存系统要求响应数据  注意:返回订单信息中一定包含: wareId 订单对应发货仓库ID
            //5.1 遍历所有拆分后子订单集合 按照格式返回
            if (!CollectionUtils.isEmpty(allSubOrderInfoList)) {
                List<Map<String, Object>> newOrderList = allSubOrderInfoList.stream().map(orderInfo -> {
                    Map<String, Object> map = initWareMap(orderInfo);
                    return map;
                }).collect(Collectors.toList());
                return JSON.toJSONString(newOrderList);
            }
        }
        return null;
    }


    /**
     * 根据订单ID查询订单以及订单明细
     *
     * @param orderId
     * @return
     */
    @Override
    public OrderInfo getOrderInfo(Long orderId) {
        //1.根据订单ID查询订单信息
        OrderInfo orderInfo = this.getById(orderId);

        //2.根据订单ID查询订单明细列表
        if (orderInfo != null) {
            LambdaQueryWrapper<OrderDetail> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(OrderDetail::getOrderId, orderId);
            List<OrderDetail> orderDetailList = orderDetailService.list(queryWrapper);
            orderInfo.setOrderDetailList(orderDetailList);
        }
        return orderInfo;
    }

    /**
     * 保存秒杀订单
     *
     * @param orderInfo
     * @return
     */
    @Override
    public Long submitSeckillOrder(OrderInfo orderInfo) {
        saveOrder(orderInfo, "2");
        return orderInfo.getId();
    }

    /**
     * 保存订单方法(抽取公共方法)
     *
     * @param orderInfo
     * @param orderType 订单类型 1:普通订单 2:秒杀订单
     */
    private void saveOrder(OrderInfo orderInfo, String orderType) {
        List<OrderDetail> orderDetailList = orderInfo.getOrderDetailList();
        //4.保存订单信息
        //4.1 封装订单表中其他信息
        orderInfo.sumTotalAmount();
        orderInfo.setOriginalTotalAmount(orderInfo.getTotalAmount());
        orderInfo.setOrderStatus(OrderStatus.UNPAID.name());
        orderInfo.setProcessStatus(ProcessStatus.UNPAID.name());
        orderInfo.setPaymentWay(PaymentType.ALIPAY.name());
        //生成订单编号(唯一)
        String outTradeNo = "";
        Calendar calendar = Calendar.getInstance();
        if ("2".equals(orderType)) {
            outTradeNo = "ATGUIGUMS" + System.currentTimeMillis() + new Random().nextInt(1000);
            calendar.add(Calendar.MINUTE, 5);
            orderInfo.setExpireTime(calendar.getTime());
        } else {
            outTradeNo = "ATGUIGU" + System.currentTimeMillis() + new Random().nextInt(1000);
            calendar.add(Calendar.DATE, 1);
            orderInfo.setExpireTime(calendar.getTime());
        }
        orderInfo.setOutTradeNo(outTradeNo);
        //获取商品名称
        if (!CollectionUtils.isEmpty(orderDetailList)) {
            String tradeBody = orderDetailList.stream().map(OrderDetail::getSkuName).collect(Collectors.joining(","));
            if (tradeBody.length() > 100) {
                orderInfo.setTradeBody(tradeBody.substring(0, 100));
            } else {
                orderInfo.setTradeBody(tradeBody);
            }
            orderInfo.setImgUrl(orderDetailList.get(0).getImgUrl());
        }
        orderInfo.setOperateTime(new Date());
        //失效时间:24小时 超过该时间订单关闭
        orderInfo.setProvinceId(1L); //正常按照前端提交地址ID查询用户地址信息
        calendar.add(Calendar.DATE, 30);
        orderInfo.setRefundableTime(calendar.getTime());
        //4.2 保存订单
        this.save(orderInfo);

        //5.保存订单明细信息
        for (OrderDetail orderDetail : orderDetailList) {
            orderDetail.setOrderId(orderInfo.getId());
            if ("1".equals(orderType)) {
                orderDetail.setSourceId(1L);
                orderDetail.setSourceType("MALL");
            } else {
                orderDetail.setSourceId(2L);
                orderDetail.setSourceType("SECKILL");
            }
        }
        //批量保存订单明细
        orderDetailService.saveBatch(orderDetailList);
    }


}
